package com.bkitmobile.kesu.appengine.storingdata.server;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;


import com.beoui.geocell.GeocellManager;
import com.beoui.geocell.model.GeocellQuery;
import com.beoui.geocell.model.Point;
import com.bkitmobile.kesu.appengine.storingdata.server.entity.*;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.GeoPt;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;

public class DataStoreManager {
	private PersistenceManager pm;
	public DataStoreManager()
	{
		pm = PMF.get().getPersistenceManager();
	}
	//return 1 if success
	//return 0 if already logged in
	//return -1 if wrong user or password.
	public int doLogin(String user,String password,boolean isAvailable)
	{
		try
		{
			User u = getUser(user);
			Date d = new Date();
			try {
				password = MD5.convert(password);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (u.getPassword().equals(password)) 
			{
				//renew();
				u.setLastUpdated(d);
				if (!u.isLoggedin()) 
				{
					u.setLoggedin(true);
					u.setAvailable(isAvailable);
				}
				pm.makePersistent(u);
				return 1;
			}
			else return -1;
		} 
		catch (Exception ex)
		{		
			return -1;
		}
	}
	public int doLogin(String user,String password)
	{
		try
		{
			User u = getUser(user);
			Date d = new Date();
			try {
				password = MD5.convert(password);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (u.getPassword().equals(password)) 
			{
				return 1;
			}
			else return -1;
		} 
		catch (Exception ex)
		{		
			return -1;
		}
	}
	/**
	 * set data when user login,log out
	 * @param user 
	 * @param loggin loggin or log out
	 * @param available or invisible
	 */
	public void setLoggin(String user,boolean loggin, boolean available)
	{
		User u = getUser(user);
		u.setLoggedin(loggin);
		u.setAvailable(available);
		updateUser(u);
	}
	/**
	 * check user login
	 * @param username user who check
	 * @return true if logged in, false if not logged in
	 * @throws NullPointerException user is not exist
	 */
	public boolean isLogined(String username) throws NullPointerException
	{
		User u = getUser(username);
		return u.isLoggedin();
	}
	/**
	 * do log out
	 * @param user user who log out
	 * @return
	 */
	public void doLogOut(String user)
	{
		User u = getUser(user);
		u.setLastUpdated(new Date());
		u.setLoggedin(false);
		u.setAvailable(false);
		//renew();
		pm.makePersistent(u);
	}
	
	public List<User> getViewInviList(String user)
	{
		User u = pm.getObjectById(User.class, user);
		Object[] lsKey = u.getUserViewInvisible().toArray();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			User tmp = pm.getObjectById(User.class, lsKey[i]);
			lsUser.add(tmp);
		}
		return lsUser;
	}

	/**
	 * remove user from friends list
	 * @param userFrom user who remove
	 * @param userDest user removed
	 */
	public void removeUser(String userFrom, String userDest)
	{
		User uFrom = getUser(userFrom);
		Key keyDest = KeyFactory.createKey(User.class.getSimpleName(), userDest);
		//de thao tac trong 1 key list thi phai getkey
		if(!uFrom.getFriendsList().remove(keyDest)){
			uFrom.getWaitingAccept().remove(keyDest);
		}
		Set setUserCanViewInvi = uFrom.getUserViewInvisible();
		if(setUserCanViewInvi != null){
			setUserCanViewInvi.remove(keyDest);
			
		}
		Set setUserViewPermison = uFrom.getViewInvisiblePermision();
		if(setUserViewPermison != null){
			setUserViewPermison.remove(keyDest);
		}
		Set manageUser = uFrom.getManageUser();
		if(setUserViewPermison != null){
			manageUser.remove(keyDest);
		}

		User uDest = getUser(userDest);
		if(!uDest.getFriendsList().remove(uFrom.getKey())){
			uDest.getIsWaitingAccept().remove(uFrom.getKey());
			Key k = KeyFactory.createKey(InviteUser.class.getSimpleName()
					, uFrom.getId()+"-"+uDest.getId());
			InviteUser inv = pm.getObjectById(InviteUser.class,k);
			uDest.getInviteCmt().remove(k);
			pm.deletePersistent(inv);
		}
		setUserCanViewInvi = uDest.getUserViewInvisible();
		if(setUserCanViewInvi != null){
			setUserCanViewInvi.remove(uFrom.getKey());
		}
		setUserViewPermison = uDest.getViewInvisiblePermision();
		if(setUserViewPermison != null){
			setUserViewPermison.remove(uFrom.getKey());
		}
		
		UserLocationData ul = getUserLocationData(userFrom);
		Set ulSee24 = ul.getUsersView();
		if(ulSee24 != null){
			ulSee24.remove(keyDest);
		}
		ul = getUserLocationData(userDest);
		ulSee24 = ul.getUsersView();
		if(ulSee24 != null){
			ulSee24.remove(uFrom.getKey());
		}
		
		pm.makePersistent(uFrom);
		pm.makePersistent(uDest);
	}
	public boolean setImage(String userName,Blob imageBlob ){
		try {
			User u = getUser(userName);
			u.setAvatar(imageBlob);
			updateUser(u);
			return true;
		} catch (Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * get user from String
	 * @param user user's name
	 * @return user
	 */
	public User getUser(String user)
	{
		Key k = KeyFactory.createKey(User.class.getSimpleName(), user);
		User u;
		User cloneUser;
		try
		{
			u = pm.getObjectById(User.class, k);
			cloneUser = pm.detachCopy(u);
		} catch (Exception e)
		{
			cloneUser = null;
		}
		return cloneUser;
	}
	/**
	 * get user from key
	 * @param k user's key
	 * @return user
	 */
	public User getUser(Key k)
	{
		User u;
		User cloneUser;
		try
		{
			u = pm.getObjectById(User.class, k);
			cloneUser = pm.detachCopy(u);
		} catch (Exception e)
		{
			cloneUser = null;
		}
		return cloneUser;
	}
	
	/**
	 * create new user
	 * @param u user will create
	 * @return 1 if success, -1 if exist
	 */
	public int createUser(User u)
	{
		UserLocationData userData = null;
		User uExist = this.getUser(u.getId());
		if (uExist != null) 
			return -1; 
		try {
            pm.makePersistent(u);
            userData = new UserLocationData(u.getId());
            userData.addNewLocation(u.getCurrentLatitude(), u.getCurrentLongitude(), u.getStatus(),true);
            pm.makePersistent(userData);
            UserConfig uConfig = new UserConfig(u.getId());
            pm.makePersistent(uConfig);
        } 
		catch (Exception e)
		{	
			pm.close(); 
			e.printStackTrace();
        }
        return 1;
	}
	public void updateUser(User u)
	{
		pm.makePersistent(u);
	}
	/**
	 * 
	 * @param user
	 * @return
	 */
	public UserLocationData getUserLocationData(String user)
	{
		Key k = KeyFactory.createKey(UserLocationData.class.getSimpleName(), user);
		UserLocationData u = pm.getObjectById(UserLocationData.class, k);
		UserLocationData clone = new UserLocationData(user);
		clone.setCurrentStatus(u.getCurrentStatus());
		clone.setUsersView(u.getUsersView());
		clone.setLocationData(u.getLocationData());
		return clone;
	}
	/**
	 * invite between users
	 * @param uFrom user who invite
	 * @param uDest user who invited
	 * @param cmt comment invite
	 * @throws Exception exception if user not found
	 */
	public void inviteUser(User uFrom,User uDest,String cmt) throws Exception
	{
		
		uFrom.getWaitingAccept().add(uDest.getKey());
		uDest.getIsWaitingAccept().add(uFrom.getKey());
		Key k = KeyFactory.createKey(InviteUser.class.getSimpleName()
				, uFrom.getId()+"-"+uDest.getId());
		InviteUser inv = new InviteUser(k, cmt,uFrom.getId(),uDest.getId());
		
		uDest.getInviteCmt().add(inv.getKey());
		//uDest.getInviteComment().put(uFrom.getKey(), cmt);
		//renew();
		pm.makePersistent(inv);
		pm.makePersistent(uFrom);
		pm.makePersistent(uDest);
	}
	/**
	 * accept invte between users
	 * @param uFrom user accept invte
	 * @param uDest user request invite
	 */
	public void acceptInvite(User uFrom,User uDest)
	{
		uFrom.getFriendsList().add(uDest.getKey());
		uFrom.getIsWaitingAccept().remove(uDest.getKey());
		//uFrom.getInviteComment().remove(uDest.getKey());
		Key k = KeyFactory.createKey(InviteUser.class.getSimpleName()
				, uDest.getId()+"-"+uFrom.getId());
		InviteUser inv = pm.getObjectById(InviteUser.class,k);
		uFrom.getInviteCmt().remove(k);
		pm.deletePersistent(inv);
		
		uDest.getWaitingAccept().remove(uFrom.getKey());
		uDest.getFriendsList().add(uFrom.getKey());
		//renew();
		PersistenceManager pm1 = PMF.get().getPersistenceManager();
		pm1.makePersistent(uFrom);
		pm1.makePersistent(uDest);		
	}
	/**
	 *  deny invite between users
	 * @param uFrom user deny invite
	 * @param uDest user request invite
	 */
	public void denyInvite(User uFrom,User uDest)
	{
		uFrom.getIsWaitingAccept().remove(uDest.getKey());
		//uFrom.getInviteComment().remove(uDest.getKey());
		Key k = KeyFactory.createKey(InviteUser.class.getSimpleName()
				, uDest.getId()+"-"+uFrom.getId());
		InviteUser inv = pm.getObjectById(InviteUser.class,k);
		uFrom.getInviteCmt().remove(k);
		pm.deletePersistent(inv);
		uDest.getWaitingAccept().remove(uFrom.getKey());
		//renew();
		PersistenceManager pm1 = PMF.get().getPersistenceManager();
		pm1.makePersistent(uFrom);
		pm1.makePersistent(uDest);	
	} 
	/**
	 * get list comments invite to user
	 * @param user 
	 * @return list comment
	 */
	public List<InviteUser> getCommentInvite(User user)
	{
		Object[] lsKey = user.getInviteCmt().toArray();
		List<InviteUser> lsUser = new LinkedList<InviteUser>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			InviteUser tmp = pm.getObjectById(InviteUser.class, lsKey[i]);
			lsUser.add(tmp);
		}
		return lsUser;
	}
	/**
	 * get user's friends list
	 * @param user
	 * @return friends list
	 */
	public List<User> getFriendsList(User user)
	{
		Object[] lsKey = user.getFriendsList().toArray();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			User tmp = pm.getObjectById(User.class, lsKey[i]);
			lsUser.add(tmp);
		}
		return lsUser;
	}
	/**
	 * get list users who this user, this user will accept/deny
	 * @param user 
	 * @return list users
	 */
	public List<User> getWaitingList(User user)
	{
		Object[] lsKey = user.getWaitingAccept().toArray();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			User tmp = pm.getObjectById(User.class, lsKey[i]);
			lsUser.add(tmp);
		}
		return lsUser;
	}
	/**
	 * get list user is waiting accept
	 * @param user 
	 * @return list user waiting accept from user
	 */
	public List<User> getIsWaitingAccept(Key user)
	{
		User u = pm.getObjectById(User.class, user);
		Object[] lsKey = u.getIsWaitingAccept().toArray();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			User tmp = pm.getObjectById(User.class, lsKey[i]);
			lsUser.add(tmp);
		}
		return lsUser;
	}
	public Key getUKey(String id)
	{
		return KeyFactory.createKey(User.class.getSimpleName(), id);
	}
	/**
	 * get list users can view 24h of this user
	 * @param u: data of location user
	 * @return list user can view the user's list location
	 */
	public List<User> getSee24hList(UserLocationData u) {
		Object[] lsKey = u.getUsersView().toArray();
		u.getUsersView();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < lsKey.length ; i++)
		{
			User tmp = pm.getObjectById(User.class, lsKey[i]);
			lsUser.add(tmp);
		}
		//TODO: more
		return lsUser;
		
	}
	public void UpdateLocationData(UserLocationData uld)
	{
		pm.makePersistent(uld);
	}
	/**
	 * find locations near user
	 * @param latitude latitude of user
	 * @param longitude longitude of user
	 * @return
	 */
	public List<Place> findNearbyLocation(double latitude,double longitude)
	{
		List<Place> objects = null;
        try {
            
        	objects = GeocellManager.proximityFetch(new Point(latitude, longitude), 100, 0.001, Place.class, null, pm);
            return objects;
        } catch (Exception e) {
        	return null;
        }
	}
	/**
	 * 
	 * @param uFrom
	 * @param uDest
	 * @param theMessage
	 */
	public void addMessage(User uFrom,User uDest,String theMessage)
	{
		Key key = KeyFactory.createKey(
				Message.class.getSimpleName(),
				uFrom.getId()+"-"+uDest.getId());
		Message message = null;
		Text txtMessage = new Text(theMessage);
		try {
			message = getMessage(key);
			message.addMessage(txtMessage);
			pm.makePersistent(message);
		} catch (Exception e)
		{
			message = new Message(uFrom.getId(), uDest.getId(),txtMessage);
			pm.makePersistent(message);
		} finally {
			uDest.getMessages().add(key);
			updateUser(uDest);
		}
	}
	/**
	 * delete message 
	 * @param uFrom user who send mesage
	 * @param uDest user who receive message
	 */
	public void deleteMessage(User uFrom, User uDest)
	{
		try {
			Key key = KeyFactory.createKey(
					Message.class.getSimpleName(),
					uFrom.getId()+"-"+uDest.getId());
			uDest.getMessages().remove(key);
			updateUser(uDest);
			deleteMessage(key);
		} catch (Exception e)
		{
			return;
		}
	}
	public Message getMessage(Key message)
	{
		return pm.detachCopy(pm.getObjectById(Message.class,message));
	}
	public void deleteMessage(Key k)
	{
		Message message = pm.getObjectById(Message.class,k);
		pm.deletePersistent(message);
	}
	/**
	 * get list users who can view this user when invisible
	 * @param user 
	 * @return list user
	 */
	public List<User> getViewInviList(User user) {
		// TODO Auto-generated method stub
		Object[] obj = user.getUserViewInvisible().toArray();
		List<User> lsUser = new LinkedList<User>();
		for (int i = 0 ; i < obj.length ; i++)
		{
			User u = getUser((Key)obj[i]);
			lsUser.add(u);
		}
		return lsUser;
	}
	/**
	 * create new place
	 * @param userCreate user who create  
	 * @param namePlace place's name
	 * @param type place's type
	 * @param description place's infomation
	 * @param addr place's address
	 * @param lng place's longitude
	 * @param lat place's latitude
	 * @param isPublic public or premium
	 * @param privacy: 1 if public, 2 if premium, 3 if favorite place, 0 if personal place
	 * @return 1 if success, -1 if fail
	 */
	private static int PUBLIC_PLACE = 1;
	private static int PREMIUM_PLACE = 2;
	private static int FAVOURITE_PLACE = 3;
	private static int PERSONAL_PLACE = 0;
	public int createNewPlace(String userCreate,String namePlace,String type,String description,String addr,
			double lng,double lat,boolean isPublic,int privacy)
	{
		User user = getUser(userCreate);
		if (!isPublic) {
			if (user.getPlaceLeft() == 0) return -1;
			user.addPlacePremium();
		}
		Place loc = new Place(user.getKey(), description, lng, lat, namePlace, addr,type);
		if (privacy == PERSONAL_PLACE)
		{
			loc.setPublic(false);
			user.getMyPlace().add(loc.getKey());
		} else user.getPlacesCreated().add(loc.getKey());
		
		loc.setListCmt(new ArrayList<CommentsLocation>());
		updateUser(user);
		updatePlace(loc);
		return 1;
	}
	/**
	 * get filter user's places (personal)
	 * @param user
	 * @return list user's personal places
	 */
	public List<Place> filterMyLocation(User user)
	{
		List<Place> myPlace = new ArrayList<Place>();
		Object[] places = user.getMyPlace().toArray();
		for (int i = 0 ; i < places.length ; i++)
		{
			Place p = getPlace((Key) places[i]);
			myPlace.add(p);
		}
		return myPlace;
	}
	/**
	 * get filter user's favorite places
	 * @param user 
	 * @return list favorite of user
	 */
	public List<Place> filterMyFavoritePlace(User user)
	{
		List<Place> myFavorite = new ArrayList<Place>();
		Object[] places = user.getFavoritePlace().toArray();
		for (int i = 0 ; i < places.length ; i++)
		{
			Place p = getPlace((Key) places[i]);
			myFavorite.add(p);
		}
		return myFavorite;
	}
	/**
	 * 
	 * @param lng: longitude of user's location
	 * @param lat: latitude of user's location
	 * @param distance:  max distance between user and place
	 * @return list of place 
	 */
	public List<Place> getPlaceNear(double lng,double lat,double distance)
	{
		Point center = new Point(lat, lng);
		List<Object> params = new ArrayList<Object>();
		params.add(true);
        GeocellQuery baseQuery = new GeocellQuery("isPublic == isPublicParam", "boolean isPublicParam", params);
		List<Place> result = null;
		try {
			result = GeocellManager.proximityFetch(center, Place.MAX_PLACE, distance, Place.class,baseQuery, pm);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return result;
	}
	/**
	 * 
	 * @param lat latitude of user
	 * @param lng longitude of user
	 * @param type filter with type
	 * @param name name of place
	 * @param distance: max distance between user and place
	 * @return list of places
	 */
	public List<Place> search(double lat,double lng,String type,String name,double distance)
	{
		List<Place> result = null;
		Point center = new Point(lat, lng);
		if (type.equals("All"))
		{
			result = getPlaceNear(lng, lat, distance);
		}
		else {
			List<Object> params = new ArrayList<Object>();
			params.add(true);
			params.add(type);
	        GeocellQuery baseQuery = 
	        	new GeocellQuery("isPublic == isPublicParam && type == typeParam", "boolean isPublicParam,String typeParam", params);
			try {
				result = GeocellManager.proximityFetch(center, Place.MAX_PLACE, distance, Place.class,baseQuery, pm);
			} catch (Exception e)
			{
				e.printStackTrace();
			}
		}
		if (name == null || name.equals("")) return result;
		else {
			List<Place> filter = new ArrayList<Place>();
			for (int i = 0 ; i < result.size() ; i++)
			{
				if (result.get(i).getName().indexOf(name)>=0)
					filter.add(result.get(i));
			}
			return filter;
		}
	}
	public Key getPlaceKey(String index)
	{
		Key key = KeyFactory.createKey(Place.class.getSimpleName(), index);
		return key;
	}
	/**
	 * 
	 * @param k get place with key k
	 * @return place
	 */
	public Place getPlace(Key k)
	{
		Place loc = pm.getObjectById(Place.class, k);
		Place cloneLoc = new Place(loc.getUserCreate(), loc.getInfo(), 
				loc.getLongtitude(), loc.getLattitude(), 
				loc.getName(), loc.getAddress(), loc.getType());
		
		cloneLoc.setListCmt(loc.getListCmt());
		return cloneLoc;
	}
	public void deletePlace(Place loc)
	{
		pm.deletePersistent(loc);
	}
	public void updatePlace(Place loc)
	{
		pm.makePersistent(loc);
	}
	/**
	 * 
	 * @param user user who comment
	 * @param lat latE6 of place
	 * @param lng lngE6 of place
	 * @param cmt String comment to place
	 */
	public void addPlaceComment(String user,int lat, int lng,String cmt)
	{
		Key k = getPlaceKey(String.valueOf(lng)+""+String.valueOf(lat));
		Place loc = getPlace(k);
		if (loc.getListCmt() == null) 
			loc.setListCmt( new ArrayList<CommentsLocation>());
		loc.getListCmt().add(new CommentsLocation(getUser(user).getKey(), cmt, user));
		updatePlace(loc);
	}
	
	/**
	 * 
	 * @param index key of place (latitudeE6 append with longitudeE6)
	 * @param user: username who like place
	 */
	public void likePlace(String index,String user)
	{
		Key k = getPlaceKey(index);
		User u = getUser(user);
		if (!u.getFavoritePlace().contains(k))
		{
			u.getFavoritePlace().add(k);	
			updateUser(u);
		}
	}
	/**
	 * 
	 * @param username of user
	 * @return user with named username
	 */
	public UserConfig getUserConfig(String username)
	{
		return pm.detachCopy(pm.getObjectById(UserConfig.class
				,KeyFactory.createKey(UserConfig.class.getSimpleName(), username)));
	}
	
	public void updateUserConfig(UserConfig uConfig)
	{
		pm.makePersistent(uConfig);
	}
	/**
	 * 
	 * @param index key of place (latitde append with longitude)
	 * @param user: username who dislike
	 */
	public void dislikePlace(String index,String user)
	{
		Key k = getPlaceKey(index);
		User u = getUser(user);
		if (u.getFavoritePlace().contains(k))
		{
			u.getFavoritePlace().remove(k);	
			updateUser(u);
		}
	}
	/**
	 * 
	 * @param username
	 * @return  get list of places user created
	 */
	public List<Place> getPlacesCreated(String username)
	{
		User user = getUser(username);
		List<Place> lsPlace = new ArrayList<Place>();
		Object[] placesCreated = user.getPlacesCreated().toArray();
		Object[] myPlaces = user.getMyPlace().toArray();
		for (int i = 0 ; i < placesCreated.length ; i++)
		{
			lsPlace.add(getPlace((Key) placesCreated[i]));
		}
		for (int j = 0 ; j < myPlaces.length ; j++)
		{
			lsPlace.add(getPlace((Key) myPlaces[j]));
		}
		return lsPlace;
	}
	/**
	 * 
	 * @param lat latitude of user
	 * @param lng longitude of user
	 * @param uConfig config of user
	 * @return list of places near user
	 */
	public List<Place> listPlaceAlert(double lat,double lng,UserConfig uConfig)
	{
		Point center = new Point(lat, lng);
		List<Object> params = new ArrayList<Object>();
		params.add(true);
		List<Place> result = null;
		String filter = "isPublic == publicParam ";
		String declare = "boolean publicParam";
		int i = 0;
		String filterType = "";
		for (String type:uConfig.getListType())
		{
			params.add(type);
			if ( i > 0 ) filterType += "|| type == typeParam"+i;
			else filterType = "type == typeParam"+i;
			declare += ",String typeParam"+i;
			i++;
		}
		filter = filter + "&& ( " + filterType +")";
        GeocellQuery baseQuery = 
        	new GeocellQuery(filter, declare, params);
        
		try {
			result = GeocellManager.proximityFetch(center, Place.MAX_PLACE_ALERT, Place.DISTANCE_ALERT, Place.class,baseQuery, pm);
			//System.out.print(filter+"\n"+declare+"\n"+result.size());
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return result;
	}
}
